package com.lemon.cloud.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.lemon.cloud.admin.dto.UserDTO;
import com.lemon.cloud.admin.entity.SysUserAtt;
import com.lemon.cloud.admin.entity.UserOrg;
import com.lemon.cloud.admin.entity.UserPost;
import com.lemon.cloud.admin.entity.UserRole;
import com.lemon.cloud.admin.mapper.SysUserMapper;
import com.lemon.cloud.admin.service.*;
import com.lemon.cloud.admin.utils.ParamResolver;
import com.lemon.cloud.admin.vo.UserVO;
import com.lemon.cloud.comm.constants.CacheConstants;
import com.lemon.cloud.comm.constants.CommonConstants;
import com.lemon.cloud.comm.constants.OAuth2Constant;
import com.lemon.cloud.comm.constants.enums.StatusEnum;
import com.lemon.cloud.comm.exception.BusinessException;
import com.lemon.cloud.comm.model.ResultMsg;
import com.lemon.cloud.comm.po.SysRole;
import com.lemon.cloud.comm.po.SysUser;
import com.lemon.cloud.core.utils.RedisUtils;
import com.lemon.cloud.core.utils.SpringContextHolder;
import com.lemon.cloud.core.utils.WebUtils;
import com.lemon.cloud.log.entity.Log;
import com.lemon.cloud.log.event.SysLogEvent;
import com.lemon.cloud.log.utils.SysLogUtils;
import com.lemon.cloud.security.model.SecurityUser;
import com.lemon.cloud.security.util.SecurityUtils;
import com.lemon.cloud.websocket.config.WebSocketMessageSender;
import com.lemon.cloud.websocket.message.MessageDO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.lemon.cloud.comm.model.ResultCode.PARAMETER_FAIL;
import static com.lemon.cloud.comm.model.ResultCode.SERVICE_EXCEPTION;

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    @Autowired
    private SysRoleService roleService;

    @Autowired
    private SysPostService postService;

    @Autowired
    private SysOrgService orgService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private UserPostService userPostService;

    @Autowired
    private UserOrgService userOrgService;

    @Autowired
    private SysUserAttService userAttService;

    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @CreateCache(name = CacheConstants.SYS_USER_DETAIL)
    private Cache<String, SysUser> userCache;

    @CreateCache(name = CacheConstants.SYS_USER_DETAIL_BY_MOBILE)
    private Cache<String, SysUser> userCacheByMobile;

    @CreateCache(name = CacheConstants.SYS_USER_ROLE)
    private Cache<Long, List<SysRole>> roleCache;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public SysUser getUserByUsername(String username) {
        if (StrUtil.isEmpty(username)) {
            return null;
        }
        return userCache.computeIfAbsent(username,(k)->{
            SysUser sysUser = baseMapper.selectByUsername(username);
            setPostOrg(sysUser);
            return sysUser;
        });
    }

    @Override
    public SysUser getByMobile(String mobile) {
        if (StrUtil.isEmpty(mobile)) {
            return null;
        }
        return userCacheByMobile.computeIfAbsent(mobile,(k)->{
            SysUser sysUser = baseMapper.getByMobile(mobile);
            setPostOrg(sysUser);
            return sysUser;
        });
    }

    private void setPostOrg(SysUser sysUser){
        if (sysUser!=null){
            sysUser.setPostList(postService.selectByUserId(sysUser.getId()));
            sysUser.setOrgList(orgService.selectByUserId(sysUser.getId()));
        }
    }
    @Override
    public ResultMsg<Boolean> register(UserDTO dto) {
        long count = this.count(new LambdaQueryWrapper<SysUser>()
                .eq(StrUtil.isNotEmpty(dto.getUsername()), SysUser::getUsername, dto.getUsername())
                .or()
                .eq(StrUtil.isNotEmpty(dto.getMobile()), SysUser::getMobile, dto.getMobile())
        );
        if (count > 0) {
            return ResultMsg.resultFail(PARAMETER_FAIL.getCode(), "用户名或手机号已存在");
        }

        String defaultRole = ParamResolver.getStr("USER_DEFAULT_ROLE");

        // 默认角色
        SysRole sysRole = roleService
                .getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getCode, defaultRole).select(SysRole::getId, SysRole::getName, SysRole::getIsSys, SysRole::getCode));

        if (sysRole == null) {
            return ResultMsg.resultFail(SERVICE_EXCEPTION.getCode(), "用户无默认角色");
        }

        dto.setRole(Collections.singletonList(sysRole.getId()));
        return ResultMsg.resultSuccess(saveUser(dto));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveUser(UserDTO userDto) {
        SysUser sysUser = new SysUser();
        BeanUtils.copyProperties(userDto, sysUser);
        sysUser.setStatus(StatusEnum.NORMAL.getValue());
        sysUser.setName(StrUtil.EMPTY);
        sysUser.setEmail(StrUtil.EMPTY);
        if (StrUtil.isEmpty(userDto.getPassword())){
            throw new IllegalArgumentException("密码不能为空");
        }
        sysUser.setPassword(passwordEncoder.encode(userDto.getPassword()));
        sysUser.setCreateTime(LocalDateTime.now());
        sysUser.setCreateBy(SecurityUtils.getUser().getUsername());
        baseMapper.insert(sysUser);
        userDto.getRole().stream().map(roleId -> {
            UserRole userRole = new UserRole();
            userRole.setUserId(sysUser.getId());
            userRole.setRoleId(roleId);
            return userRole;
        }).forEach(userRoleService::save);
        if (CollectionUtil.isNotEmpty(userDto.getPost())) {
            userDto.getPost().forEach(postId -> {
                UserPost userPost = new UserPost();
                userPost.setUserId(sysUser.getId());
                userPost.setPostId(postId);
                userPostService.save(userPost);
            });
        }
        if (CollectionUtil.isNotEmpty(userDto.getOrg())) {
            userDto.getOrg().forEach(orgId -> {
                UserOrg userOrg = new UserOrg();
                userOrg.setUserId(sysUser.getId());
                userOrg.setOrgId(orgId);
                userOrgService.save(userOrg);
            });
        }
        return Boolean.TRUE;
    }

    @Override
    public IPage<UserVO> getUserByPage(Page<UserVO> page, UserDTO userDTO) {
        IPage<SysUser> userByPage = baseMapper.getUserByPage(page, userDTO);
        page.setTotal(userByPage.getTotal());
        List<UserVO> list = Lists.newArrayList();
        List<SysUser> records = userByPage.getRecords();
        if (CollectionUtil.isNotEmpty(records)) {
            records.forEach(item -> {
                UserVO vo = new UserVO();
                BeanUtils.copyProperties(item, vo);
                String key = OAuth2Constant.USER_ONLINE_PREFIX+item.getId();
                boolean online = RedisUtils.hasKey(key);
                if (online) {
                    Set<String> onlineSet = Objects.requireNonNull(redisTemplate.opsForSet().members(key)).stream()
                            .map(i -> i.toString().split(StrUtil.COMMA)[0]).collect(Collectors.toSet());
                    vo.setOnlineValue(onlineSet);
                }
                vo.setOnline(online);
                list.add(vo);
            });
        }
        page.setRecords(list);
        return page;
    }

    /**
     * 用户修改个人信息
     * @param userDto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean editUser(UserDTO userDto) {
        SecurityUser user = SecurityUtils.getUser();
        Long id = user.getId();
        //修改邮箱、手机号
        SysUser sysUser = new SysUser();
        sysUser.setId(id);
        sysUser.setEmail(userDto.getEmail());
        sysUser.setMobile(userDto.getMobile());
        sysUser.setUpdateTime(LocalDateTime.now());
        sysUser.setUpdateBy(user.getUsername());

        this.updateById(sysUser);
        //更新额外的信息
        SysUserAtt sysUserAtt = new SysUserAtt();
        BeanUtils.copyProperties(userDto,sysUserAtt);
        sysUserAtt.setId(user.getId());
        sysUserAtt.setUpdateTime(LocalDateTime.now());
        sysUserAtt.setUpdateBy(user.getUsername());
        userAttService.saveOrUpdate(sysUserAtt);

        removeCache(user.getUsername(),null);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateUser(UserDTO userDto) {
        SysUser sysUser = new SysUser();
        BeanUtils.copyProperties(userDto, sysUser);
        sysUser.setUpdateTime(LocalDateTime.now());
        sysUser.setUpdateBy(SecurityUtils.getUser().getUsername());
        if (StrUtil.isNotBlank(userDto.getPassword())) {
            sysUser.setPassword(passwordEncoder.encode(userDto.getPassword()));
        }

        this.updateById(sysUser);
        userRoleService.remove(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, userDto.getId()));
        List<UserRole> userRoleList = Lists.newArrayList();
        userDto.getRole().forEach(roleId -> {
            UserRole userRole = new UserRole();
            userRole.setUserId(userDto.getId());
            userRole.setRoleId(roleId);
            userRoleList.add(userRole);
        });
        userRoleService.saveBatch(userRoleList);

        userPostService.remove(new LambdaQueryWrapper<UserPost>().eq(UserPost::getUserId, userDto.getId()));
        List<UserPost> userPostList = Lists.newArrayList();
        userDto.getPost().forEach(postId -> {
            UserPost userPost = new UserPost();
            userPost.setUserId(userDto.getId());
            userPost.setPostId(postId);
            userPostList.add(userPost);
        });
        userPostService.saveBatch(userPostList);

        userOrgService.remove(new LambdaQueryWrapper<UserOrg>().eq(UserOrg::getUserId, userDto.getId()));
        List<UserOrg> userOrgList = Lists.newArrayList();
        userDto.getOrg().forEach(orgId -> {
            UserOrg userOrg = new UserOrg();
            userOrg.setUserId(userDto.getId());
            userOrg.setOrgId(orgId);
            userOrgList.add(userOrg);
        });
        userOrgService.saveBatch(userOrgList);

        removeCache(userDto.getUsername(),userDto.getId());
        return Boolean.TRUE;
    }

    public void removeCache(String username,Long userId){
        userCache.remove(username);
        if (userId!=null){
            roleCache.remove(userId);
        }
    }
    /**
     * 强制下线
     *
     * @param id
     * @param client
     */
    @Override
    public void forceOffline(Long id, String client) {
        String key = OAuth2Constant.USER_ONLINE_PREFIX+id;
        boolean online = RedisUtils.hasKey(key);
        if (!online){
            throw new BusinessException("该用户未上线");
        }
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        Set<Object> members = setOperations.members(key);
        MessageDO messageDO = new MessageDO();
        List<Object> keys = Lists.newArrayList();
        if (CollectionUtil.isNotEmpty(members)){
            members.forEach(i->{
                String s = i.toString();
                String[] split = s.split(StrUtil.COMMA);
                if (client.equals(split[0])){
                    if (split.length>1){
                        setOperations.remove(key,i);
                        keys.add(split[1]);
                    }
                }
            });
        }
        messageDO.setType("offline");
        messageDO.setMessageText("强制下线");
        messageDO.setSessionKeys(keys);
        if (CollectionUtil.isEmpty(messageDO.getSessionKeys())){
            return;
        }

        handle(keys,SecurityUtils.getAuthentication());
        WebSocketMessageSender.send(messageDO.getSessionKeys().get(CommonConstants.ZERO), JSONUtil.toJsonStr(messageDO));
    }

    /**
     * 处理强制退出成功方法
     * <p>
     * 获取到登录的authentication 对象
     * @param keys
     * @param authentication 登录对象
     */
    public void handle(List<Object> keys,Authentication authentication) {
        List<String> tokenList = Lists.newArrayList();
        keys.forEach(key->{
            String token = String.format("%s::%s::%s", OAuth2ParameterNames.TOKEN, OAuth2ParameterNames.ACCESS_TOKEN, key);
            OAuth2Authorization auth2Authorization = (OAuth2Authorization)redisTemplate.opsForValue().get(token);
            String refreshToken = String.format("%s::%s::%s", OAuth2ParameterNames.TOKEN, OAuth2ParameterNames.REFRESH_TOKEN, auth2Authorization.getRefreshToken().getToken().getTokenValue());
            tokenList.add(refreshToken);
            tokenList.add(token);
        });

        redisTemplate.delete(tokenList);
        Log logVo = SysLogUtils.getSysLog();
        logVo.setTitle("强制退出");
        // 发送异步日志事件
        Long startTime = System.currentTimeMillis();
        Long endTime = System.currentTimeMillis();
        logVo.setTime(endTime - startTime);

        // 设置对应的token
        WebUtils.getRequest().ifPresent(request -> logVo.setParams(request.getHeader(HttpHeaders.AUTHORIZATION)));

        // 这边设置ServiceId
        if (authentication instanceof PreAuthenticatedAuthenticationToken) {
            logVo.setServiceId(authentication.getCredentials().toString());
        }
        logVo.setCreateBy(authentication.getName());
        SpringContextHolder.publishEvent(new SysLogEvent(logVo));
    }
}

